<?php


namespace Lib;

class Psr4AutoloaderClass
{
    /**
     * 关联数组，键为命名空间前缀，值为一个基本目录数组
     * @var array
     */
    protected $prefixes = array();

    /**
     * 通过 SPL 自动加载器栈注册加载器
     */
    public function register()
    {
        spl_autoload_register(array($this, 'loadClass'));
    }

    /**
     * 为命名空间前缀添加基本目录
     * @param $prefix   命名空间前缀
     * @param $base_dir     命名空间下类文件的基本目录
     * @param bool $prepend     是否预先追加
     */
    public function addNamespace($prefix, $base_dir, $prepend = false)
    {
        // 规范化命名空间前缀
        $prefix = trim($prefix, '\\') . '\\';

        // 规范化尾部文件分隔符
        $base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';

        // 初始化命名空间前缀数组
        if (isset($this->prefixes[$prefix]) === false) {
            $this->prefixes[$prefix] = array();
        }

        // 保留命名空间前缀的基本目录
        if ($prepend) {
            array_unshift($this->prefixes[$prefix], $base_dir);
        } else {
            array_push($this->prefixes[$prefix], $base_dir);
        }
    }

    public function loadClass($class)
    {
        // 当前命名空间前缀
        $prefix = $class;
        // 通过完整的命名空间反向映射文件名
        while (false !== $pos = strrpos($prefix, '\\')) {
            // 在前缀中保留命名空间分隔符
            $prefix = substr($class, 0, $pos + 1);
            // 其余的是相关类名
            $relative_class = substr($class, $pos + 1);
            // 尝试为前缀和相关类文件加载映射文件
            $mapped_file = $this->loadMappedFile($prefix, $relative_class);
            if ($mapped_file) {
                return $mapped_file;
            }
            // 删除strrpos下次迭代尾部命名空间分隔符
            $prefix = rtrim($prefix, '\\');
        }

        // 找不到映射文件
        return false;
    }

    /**
     * 为命名空间前缀和相关类加载映射文件
     * @param $prefix
     * @param $relative_class
     * @return bool|string
     */
    protected function loadMappedFile($prefix, $relative_class)
    {
        // 命名空间前缀是否存在任何基本目录
        if (isset($this->prefixes[$prefix]) === false) {
            return false;
        }

        // 通过基本目录查找命名空间前缀
        foreach ($this->prefixes[$prefix] as $base_dir) {
            // 用基本目录替换命名空间前缀
            // 用目录分隔符替换命名空间分隔符
            // 加上 .php 后缀
            $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
            // 如果映射文件存在，则引入
            if ($this->requireFile($file)) {
                return $file;
            }
        }

        return false;
    }

    /**
     * 如果文件存在从系统在引入
     * @param $file
     * @return bool
     */
    protected function requireFile($file)
    {
        if (file_exists($file)) {
            require  $file;
            return true;
        }
        return false;
    }
}